# 配置HTML模版

在构建的过程中，EMP 会基于 HTML 模板文件和模板参数进行编译，生成一个类似于 SPA 的构建产物。

EMP 提供了一些配置项来对 HTML 模板进行设置。通过本章节你可以了解到这些配置项的基本用法。

## 设置模板文件

EMP 默认使用自带的 HTML 模板文件，当然, EMP 允许您指定一个 HTML 文件作为模板文件代替默认的 HTML 模板文件进行产物构建运行，您可以使用 `html.template` 配置项来设置它：

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      template: './index.html',
    },
  }
})
```

上述示例代码会查找当前 `emp.config.js` 同一级目录下的 `index.html` 作为模板文件。

:::warning ⚠️ 注意
配置项 `html.template` 的值只能是相对路径，请勿使用绝对路径，默认值为 `src/index.html`。
:::

### 模板文件示例

为了您的正常使用，您应该将HTML模板设置完整，一个最小的模板文件应该是这样的：

```html title="min.html"
<!DOCTYPE html>
<html lang="<%= htmlWebpackPlugin.options.lang %>">

<head>
  <title>
    <%= htmlWebpackPlugin.options.title %>
  </title>
</head>

<body>
  <div id="emp-root"></div>
</body>

</html>
```

当然，您也可以进行在此基础上进行优化，进行兼容性等相关处理，完整的示例如下：

```html title="index.html"
<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="ie6" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 7 ]>    <html lang="en" class="ie7" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 8 ]>    <html lang="en" class="ie8" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 9 ]>    <html lang="en" class="ie9" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <!--<![endif]-->
<head>
  <meta charset="utf-8">
  <title><%= htmlWebpackPlugin.options.title || 'Webpack App'%></title>

  <% if (htmlWebpackPlugin.files.favicon) { %>
  <link rel="shortcut icon" href="<%= htmlWebpackPlugin.files.favicon%>">
  <% } %>
  <% if (htmlWebpackPlugin.options.mobile) { %>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <% } %>

  <% for (var css in htmlWebpackPlugin.files.css) { %>
  <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
  <% } %>
</head>
<body>
<% if (htmlWebpackPlugin.options.unsupportedBrowser) { %>
<style>.unsupported-browser { display: none; }</style>
<div class="unsupported-browser">
  Sorry, your browser is not supported.  Please upgrade to
  the latest version or switch your browser to use this site.
  See <a href="http://outdatedbrowser.com/">outdatedbrowser.com</a>
  for options.
</div>
<% } %>

<% if (htmlWebpackPlugin.options.appMountId) { %>
<div id="<%= htmlWebpackPlugin.options.appMountId%>"></div>
<% } %>

<% if (htmlWebpackPlugin.options.appMountIds && htmlWebpackPlugin.options.appMountIds.length > 0) { %>
<% for (var index in htmlWebpackPlugin.options.appMountIds) { %>
<div id="<%= htmlWebpackPlugin.options.appMountIds[index]%>"></div>
<% } %>
<% } %>

<% if (htmlWebpackPlugin.options.window) { %>
<script>
  <% for (var varName in htmlWebpackPlugin.options.window) { %>
    window['<%=varName%>'] = <%= JSON.stringify(htmlWebpackPlugin.options.window[varName]) %>;
  <% } %>
</script>
<% } %>

<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>

<% if (htmlWebpackPlugin.options.devServer) { %>
<script src="<%= htmlWebpackPlugin.options.devServer%>/webpack-dev-server.js"></script>
<% } %>

<% if (htmlWebpackPlugin.options.googleAnalytics) { %>
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');


  <% if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
    ga('create', '<%= htmlWebpackPlugin.options.googleAnalytics.trackingId%>', 'auto');
    <% } else { throw new Error("html-webpack-template requires googleAnalytics.trackingId config"); }%>

  <% if (htmlWebpackPlugin.options.googleAnalytics.pageViewOnLoad) { %>
    ga('send', 'pageview');
  <% } %>
</script>
<% } %>
</body>
</html>
```

## 设置页面标题

当我们只需要改动页面标题时，我们可以直接通过 `html.title` 配置项来设置它：

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      title: 'EMP3 666',
    },
  }
})
```

上述示例代码会将 HTML 模板文件中的`<title>` 标签设置为 `EMP3 666`。

## 设置页面语言

当我们只需要改动页面语言时，我们可以直接通过 `html.lang` 配置项来设置它：

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      lang: 'en-US',
    },
  }
})
```

上述示例代码会将 HTML 模板文件中的`<lang>` 标签设置为 `en-US`。

## 设置页面meta标签

当我们只需要设置meta标签时，我们可以直接通过 `html.meta` 配置项来设置它：

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      meta: {
        viewport: 'width=device-width, initial-scale=1.0',
      },
    },
  }
})
```

上述示例代码会将 HTML 模板文件中的`<meta>` 标签设置相关视口属性。

## 设置页面图标

当我们只需要设置页面图标时，我们可以直接通过 `html.favicon` 配置项来设置它：

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      favicon: './favicon.ico',
    },
  }
})
```

上述示例代码会查找当前 `emp.config.js` 同一级目录下的 `favicon.ico` 作为页面图标。

:::warning ⚠️ 注意
配置项 `html.favicon` 的值只能是相对路径，请勿使用绝对路径，默认值为 `src/favicon.ico`。
:::

## 设置模板参数

:::warning ⚠️ 注意
使用模板参数需要您在项目中正确嵌入了模板参数，或者使用了默认的模板文件，并且使用的模板渲染引擎为 `Lodash Template`。
:::

在 HTML 模板中，你可以使用丰富的模板参数用于HTML模板中，EMP默认注入的参数有：
```ts 
type DefaultParameters = {
  mountId: string; // 对应 html.mountId 配置
  entryName: string; // 入口名称
  assetPrefix: string; // 对应 dev.assetPrefix 和 output.assetPrefix 配置
  compilation: Compilation; // 对应 Rspack 的 compilation 对象
  // htmlWebpackPlugin 内置的参数
  // 详见 https://github.com/jantimon/html-webpack-plugin
  htmlWebpackPlugin: {
    tags: object;
    files: object;
    options: object;
  };
};
```

您也可以通过 `html.templateParameters` 配置项来传入自定义的模板参数：

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      templateParameters: {
        title: 'EMP3 wow',
      },
    },
  }
})
```

接下来，您可以在 HTML 模板中，通过 `<%= text %>` 来读取参数：

```html 
<div><%= text %>!</div>
```

最终经过 EMP 编译后，它的编译结果如下：

```html
<div>EMP3 wow!</div>
```

## 模板引擎

EMP 内置了三套模板引擎，可用于动态化生成HTML内容，默认使用最基础的 `Lodash Template` 作为模板引擎。

### Lodash Template

当模板文件的后缀为 `.html` 时，EMP 会使用 `Lodash Template` 对模板进行编译。

例如，在模板中定义一个 text 参数，值为 'EMP3 wow'，在构建时会自动将 `<%= text %>` 替换为对应的值。

```html 
<!-- 输入 -->
<div><%= text %>!</div>

<!-- 输出 -->
<div>EMP3 wow!</div>
```

:::tip 💡 TIP 
您可以阅读 [Lodash Template](https://lodashjs.com/docs/lodash.template) 来了解完整用法。
:::

### EJS文件

当模板文件的后缀为 `.ejs` 时，EMP 会使用 `EJS` 模板引擎对模板进行编译。EJS 是一套简单的模板语言，支持直接在标签内书写简单、直白的 JavaScript 代码，并通过 JavaScript 输出最终所需的 HTML。

例如，你可以先通过 `html.template` 配置项来引用一个 .ejs 模板文件：

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      template: './index.ejs',
    },
  }
})
```

接着在模板中定义一个 `user` 参数，值为 `{ name: 'Youli' }`。在构建时，会自动将 `<%= user.name %>` 替换为对应的值。

```html 
<!-- 输入  -->
<% if (user) { %>
<h2><%= user.name %></h2>
<% } %>

<!-- 输出 -->
<h2>Youli</h2>
```

:::tip 💡 TIP 
您可以阅读 [EJS](https://ejs.co/) 来了解完整用法。
:::

### Pug

EMP 通过 Pug 插件来支持 Pug 模板引擎，请阅读 [Pug 插件文档](https://rsbuild.dev/zh/plugins/list/plugin-pug) 来了解用法。

## 注入标签

通过配置 `html.tags` 选项可以在最终生成的 HTML 产物中插入任意标签。

:::details 🛠️ 使用场景
前端应用的产物最终都会直接或间接地被 HTML 入口引用，但大多数时候直接向 HTML 注入标签都并非首选。
:::

模版文件中可以通过 `htmlWebpackPlugin.tags` 变量来访问需要最终注入到 HTML 的所有标签：

```html 
<html>
  <head>
    <%= htmlWebpackPlugin.tags.headTags %>
  </head>
  <body>
    <%= htmlWebpackPlugin.tags.bodyTags %>
  </body>
</html>
```

`html.tags` 的作用就是调整这些模板变量进而修改 HTML，配置的具体定义参考 [API References](https://www.baidu.com/)。

### 对象用法

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      tags: {
        headTags: [
          {tag: 'script', attrs: {src: 'a.js'}},
          {tag: 'script', attrs: {src: 'b.js'}},
          {tag: 'link', attrs: {href: 'style.css', rel: 'stylesheet'}},
          {tag: 'link', attrs: {href: 'page.css', rel: 'stylesheet'}, publicPath: false},
          {tag: 'meta', attrs: {name: 'referrer', content: 'origin'}},
        ],
        bodyTags: [{tag: 'script', attrs: {src: 'c.js'}}],
      },
    },
  }
})
```

标签最终的插入位置由 `headTags` 和 `bodyTags` 选项决定，两个配置相同的元素将被插入到相同区域，并且维持彼此之间的相对位置。

标签默认会启用 `publicPath` 配置，即会将 `output.assetPrefix` 的值拼接到 `script` 标签的 `src` 等表示路径的属性上。

所以以上配置构建出的 HTML 产物将会类似：

```html 
<html>
  <head>
    <script src="https://example.com/b.js"></script>
    <link href="https://example.com/style.css" rel="stylesheet" />
    <link href="page.css" rel="stylesheet" />
    <!-- some other headTags... -->
    <script src="https://example.com/a.js"></script>
    <meta name="referrer" content="origin" />
  </head>
  <body>
    <!-- some other bodyTags... -->
    <script src="https://example.com/c.js"></script>
  </body>
</html>
```

### 函数用法

html.tags 也支持传入回调函数，常用于修改标签列表或是在插入标签的同时确保其相对位置。

```js title="emp.config.js"
export default defineConfig(store => {
  return {
    html: {
      tags: {
        headTags: [
          (tags) => {
            tags.splice(0, 1);
          },
          { tag: 'script', attrs: { src: 'a.js' }},
          { tag: 'script', attrs: { src: 'b.js' }},
          { tag: 'script', attrs: { src: 'c.js' } },
          (tags) => [...tags, { tag: 'script', attrs: { src: 'd.js' } }],
        ],
        bodyTags: [],
      },
    },
  }
})
```

最终产物将会类似：

```html 
<html>
  <head>
    <!-- some other headTags... -->
    <script src="https://example.com/c.js"></script>
    <script src="https://example.com/d.js"></script>
    <script src="https://example.com/a.js"></script>
  </head>
  <body>
  </body>
</html>
```